package com.brand.data.util;

import java.util.Arrays;

/**
 * @version 1.0
 * @author: zwg.BlueOcean
 * @date 2018/3/7 15:59
 * @description
 */
public class AESUtil {
	private static final String BASE64_CODE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	private static int[] sBox = {0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
	                               0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
	                               0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
	                               0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
	                               0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
	                               0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
	                               0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
	                               0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
	                               0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
	                               0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
	                               0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
	                               0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
	                               0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
	                               0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
	                               0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
	                               0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16};
	private static int[][] rCon={ {0x00, 0x00, 0x00, 0x00},
	                            {0x01, 0x00, 0x00, 0x00},
	                            {0x02, 0x00, 0x00, 0x00},
	                            {0x04, 0x00, 0x00, 0x00},
	                            {0x08, 0x00, 0x00, 0x00},
	                            {0x10, 0x00, 0x00, 0x00},
	                            {0x20, 0x00, 0x00, 0x00},
	                            {0x40, 0x00, 0x00, 0x00},
	                            {0x80, 0x00, 0x00, 0x00},
	                            {0x1b, 0x00, 0x00, 0x00},
	                            {0x36, 0x00, 0x00, 0x00} };
	// rotate 4-byte word w left by one byte
	private static int[] rotWord(int[] word){
		int tmp = word[0];
		for (int i = 0; i < 3; i++) {
			word[i] = word[i+1];
		}
		word[3] = tmp;
		return word;
	}
	// apply SBox to 4-byte word w
	private static int[] subWord(int[] word){
		for (int i = 0; i < 4; i++) {
			word[i] = sBox[word[i]];
		}
		return word;
	}
	// xor Round Key into state S [§5.1.4]
	private static int[][] addRoundKey(int[][] state,int[][] words,int rnd,int nub){
		for (int r = 0; r < 4; r++) {
			for (int c = 0; c < nub; c++){
				state[r][c] ^= words[rnd * 4 + c][r];
			}
		}
		return state;
	}
	// combine bytes of each col of state S [§5.1.3]
	private static int[][] mixColumns(int[][] state,int nub){
		for (int c=0; c<4; c++) {
		    int[] a = new int[4];  // 'a' is a copy of the current column from 's'
		    int[] b = new int[4];  // 'b' is a•{02} in GF(2^8)
		    for (int i=0; i<4; i++) {
		      a[i] = state[i][c];
		      b[i] = (state[i][c]&0x80)!=0 ? state[i][c]<<1 ^ 0x011b : state[i][c]<<1;

		    }
		    // a[n] ^ b[n] is a•{03} in GF(2^8)
		    state[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
		    state[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
		    state[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
		    state[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
		  }
		  return state;
	}
	// shift row r of state S left by r bytes [§5.1.2]
	private static int[][] shiftRows(int[][] state,int nub){
		int[] t = new int[4];
		for (int r = 1; r < 4; r++) {
			for (int c = 0; c < 4; c++) {
				t[c] = state[r][(c + r) % nub];
			}
			for (int c = 0; c < 4; c++)
				state[r][c] = t[c];
		}
		return state;
	}
	// apply SBox to state S [§5.1.1]
	private static int[][] subBytes(int[][] state,int nub){
		for (int r = 0; r < 4; r++) {
			for (int c = 0; c < nub; c++) {
				state[r][c] = sBox[state[r][c]];
			}
		}
		return state;
	}
	//generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
	private static int[][] keyExpansion(int[] key){
		int Nb = 4; // block size (in words): no of columns in state (fixed at 4
					// for AES)
		int Nk = key.length / 4;// key length (in words): 4/6/8 for
								// 128/192/256-bit keys
		int Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys

		int w[][] = new int[Nb * (Nr + 1)][];
		int temp[] = new int[4];

		for (int i = 0; i < Nk; i++) {
			int[] r = { key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3] };
			w[i] = r;
		}

		for (int i = Nk; i < (Nb * (Nr + 1)); i++) {
			w[i] = new int[4];
			for (int t = 0; t < 4; t++){
				temp[t] = w[i - 1][t];
			}
			if (i % Nk == 0) {
				temp = subWord(rotWord(temp));
				for (int t = 0; t < 4; t++){
					temp[t] ^= rCon[i / Nk][t];
				}
			} else if (Nk > 6 && i % Nk == 4) {
				temp = subWord(temp);
			}
			for (int t = 0; t < 4; t++) {
				w[i][t] = w[i - Nk][t] ^ temp[t];
			}
		}

		return w;
	}
	private static int[] cipher(int[] input,int[][] words){
		int Nb = 4;               // block size (in words): no of columns in state (fixed at 4 for AES)
		int Nr = words.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

		int[][] state =new int[4][4]; // initialise 4xNb byte-array 'state' with input [§3.4]
		  for (int i=0; i<4*Nb; i++){
			  //Math.floor全舍取整
			  try {
				state[i%4][i/4] = input[i];
			} catch (Exception e) {
				e.printStackTrace();
			}
		  }

		  state = addRoundKey(state,words, 0, Nb);

		  for (int round=1; round<Nr; round++) {
		    state = subBytes(state, Nb);
		    state = shiftRows(state, Nb);
		    state = mixColumns(state, Nb);
		    state = addRoundKey(state,words, round, Nb);
		  }

		  state = subBytes(state, Nb);
		  state = shiftRows(state, Nb);
		  state = addRoundKey(state, words, Nr, Nb);

		  int[] output = new int[4*Nb];  // convert state to 1-d array before returning [§3.4]
		  for (int i=0; i<4*Nb; i++){
			  output[i] = state[i%4][i/4];
		  }
		  return output;
	}
	/**
	 * 解密
	 * @param content
	 * @param pass
	 * @param nBits
	 * @return
	 */
	public static String decrypt(String content,String pass,int nBits){
		int blockSize = 16;
		if (!(nBits==128 || nBits==192 || nBits==256)){
			return "";
		}
		//Base64解码
		String ciphertext = base64decode(content);
		int nBytes = nBits/8;
		if (pass.length() < nBytes) {
			pass+=new String(new byte[nBytes-pass.length()]);
		}
		int[] pwBytes = new int[nBytes];
		for (int i = 0; i < nBytes; i++) {
			pwBytes[i] = pass.charAt(i);
		}
		
		int[][] ks = keyExpansion(pwBytes);
		int[] key = cipher(pwBytes, ks);
		int[] temp = Arrays.copyOfRange(key, 0, nBytes-16);
		int[] k = new int[key.length+temp.length];
		for (int i = 0; i < key.length; i++) {
			k[i] = key[i];
			if (temp.length>0) {
				for (int j = 0; j < temp.length; j++) {
					k[j+key.length]=temp[j];
				}
			}
		}
		int[] counterBlock = new int[16];
		for (int i = 0; i < 8; i++) {
			counterBlock[i] = ciphertext.charAt(i);
		}
		
		int[][] keySchedule = keyExpansion(k);
		int nBlocks = (int) Math.ceil((double)(ciphertext.length()-8)/blockSize);
		
		String[] ct = new String[nBlocks];
		for (int b=0; b<nBlocks; b++) {
			int end = 8 + b * blockSize + blockSize;
			end = end > ciphertext.length() ? ciphertext.length() : end;
			try {
				ct[b] = ciphertext.substring(8 + b * blockSize, end);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		StringBuffer plaintxt = new StringBuffer();
		
		for (int b = 0; b < nBlocks; b++) {
			for (int c=0; c<4; c++) {
		    	counterBlock[15-c] = ((b) >>> c*8) & 0xff;
		    }
		    for (int c=0; c<4; c++){
		    	//counterBlock[15-c-4] =  (((int)((b+1)/4294967296L-1) >>> c*8) & 0xff);
		    	counterBlock[15-c-4] = 0;
		    }
		    int[] cipherCntr = cipher(counterBlock, keySchedule);
		    
		    StringBuffer plaintxtByte = new StringBuffer();
		    for (int i=0; i<ct[b].length(); i++) {
		      // -- xor plaintxt with ciphered counter byte-by-byte --
		    	int t = cipherCntr[i] ^ ct[b].charAt(i);
		    	plaintxtByte.append(fromCharCode(t));
		    }
		    plaintxt.append(plaintxtByte);
		}
		return plaintxt.toString();
	}
	/**
	 * 加密
	 * @param content
	 * @param pass
	 * @param nBits
	 * @return
	 */
	public static String encrypt(String content,String pass,int nBits){
		int blockSize = 16;
		if (!(nBits==128 || nBits==192 || nBits==256)){
			return "";
		}
		int nBytes = nBits/8;
		if (pass.length() < nBytes) {
			pass+=new String(new byte[nBytes-pass.length()]);
		}
		int[] pwBytes = new int[nBytes];
		for (int i = 0; i < nBytes; i++) {
			pwBytes[i] = pass.charAt(i);
		}
		
		int[][] ks = keyExpansion(pwBytes);
		int[] key = cipher(pwBytes, ks);
		int[] temp = Arrays.copyOfRange(key, 0, nBytes-16);
		int[] k = new int[key.length+temp.length];
		for (int i = 0; i < key.length; i++) {
			k[i] = key[i];
			if (temp.length>0) {
				for (int j = 0; j < temp.length; j++) {
					k[j+key.length]=temp[j];
				}
			}
		}
		
		int[] counterBlock = new int[blockSize];
		
		
		long nonce = System.currentTimeMillis();
//		long nonce = 1524650155883L;
		int nonceMs = (int) (nonce%1000);
		long nonceSec = nonce/1000;
		long nonceRnd = (long) Math.floor(Math.random()*0xffff);
//		int nonceRnd = 23493;
		for (int i = 0; i < 2; i++) {
			counterBlock[i] = (int) ((nonceMs >>> i * 8) & 0xff);
		}
		for (int i = 0; i < 2; i++) {
			counterBlock[i + 2] = (int) ((nonceRnd >>> i * 8) & 0xff);
		}
		for (int i = 0; i < 4; i++) {
			counterBlock[i + 4] = (int) ((nonceSec >>> i * 8) & 0xff);
		}
		StringBuffer ctrTxt = new StringBuffer();
		for (int i=0; i<8; i++){
			ctrTxt.append(fromCharCode(counterBlock[i]));
		}
		int[][] keySchedule = keyExpansion(k);
		int blockCount = (int) Math.ceil((double)content.length()/blockSize);
		
		StringBuffer ciphertxt = new StringBuffer();
		for (int b=0; b<blockCount; b++) {
		    // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
		    // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
		    for (int c=0; c<4; c++){
		    	counterBlock[15-c] = (b >>> c*8) & 0xff;
		    }
		    for (int c=0; c<4; c++){
		    	counterBlock[15-c-4] = 0;
		    }
		    int[] cipherCntr = cipher(counterBlock, keySchedule);
		    // block size is reduced on final block
		    int blockLength = b<blockCount-1 ? blockSize : (content.length()-1)%blockSize+1;
		    //System.out.println(b+">"+(blockCount-1)+">"+blockSize+">"+blockLength+">"+(content.length()-1));
		    StringBuffer cipherChar = new StringBuffer();
		    for (int i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter char-by-char --
		    	int t = cipherCntr[i] ^ content.charAt(b*blockSize+i);
		      cipherChar.append(fromCharCode(t));
		    }
		    ciphertxt.append(cipherChar);
		  }
		ctrTxt.append(ciphertxt);
		//System.out.println(blockCount);
		return base64encode(ctrTxt.toString());
	}
	public static void main(String[] args) {
		String content="cwPFW6tQ4FpNw190ODFmnBFMlRJwhSp1dFP7j2B05JM6XJg0IQI1nVoa5Jr6cADaOjon+nz72v13aGcoEDLicd+uX+txFwqKnsCvTMbyoywxIGMq3C1WwcUdnOkZ40LQakEekhb159M0LY4QdlxLlb20rPT/m/0g5gvcgReIKsgiodJbVE2PM3U4q58Z+F/p8a9SRFNeT2cSAzo9V53dTAoJc9jZ+S8rUdzafiaMcl/e+0yMy14hxTxwOQ9hubsUlqY9xebh+wtjKQQue7kQoEUAQgij2Fmy43vbiNbm2I3u2rgZMQSi8FOWPWEnEg==";
//		String content="JAPmZB/c31oK2iTnxH/5RzqXF7rnHnjb7XkTMf0GsopSBOKe9ragJx8aZrmGcz741k2KOZVOnRmiYCy9x88MJqDf10ZK4LFlRFmAkxlH6NvMS804Jb34g9qQsJSfGAcaYVe6BnX1swTqD0ifD4NQdRtnXNb4Agte1kzE5bFH5p4US9J8F0HohpV87d/72Zh0X7uVhC/vlzDAq8webRoLHDYH/+uxr47IivI6U5RhCYe4bMU3PyKOCWGEA1ha1KVIqU7lCBvk41M2NvRqwAe8JHfYm+m3OMe8/ktcxdYe2JhGVHz3Jfr/IFGxDptlKA==";
		String pass = "cd8ab2722b38900b1632bcf75e3347b6";
		content = decrypt(content, pass, 128);
		content = encrypt(content, pass, 128);
		content = decrypt(content, pass, 128);
		System.out.println(content);
	}
	
	private static String base64decode(String str){
		int h1,h2,h3,h4,bits,o1,o2,o3;
		StringBuffer d = new StringBuffer();
		for (int i = 0; i < str.length(); i+=4) {
			h1 = BASE64_CODE.indexOf(str.charAt(i));
			h2 = BASE64_CODE.indexOf(str.charAt(i+1));
		    h3 = BASE64_CODE.indexOf(str.charAt(i+2));
		    h4 = BASE64_CODE.indexOf(str.charAt(i+3));
		    bits = h1<<18 | h2<<12 | h3<<6 | h4;
		    o1 = bits>>>16 & 0xff;
		    o2 = bits>>>8 & 0xff;
		    o3 = bits & 0xff;
		    
		    String code = "";
		    if (h4 == 0x40){
		    	code = fromCharCode(o1, o2);
		    }else if (h3 == 0x40){
		    	code = fromCharCode(o1);
		    }else{
		    	code = fromCharCode(o1, o2, o3);
		    }
		    d.append(code);
		}
		return d.toString();
		
	}
	private static String base64encode(String str){
		int h1,h2,h3,h4,bits,o1,o2,o3,c;
		StringBuffer pad = new StringBuffer();
		c = str.length()%3;
		if (c > 0) {
			while (c++ < 3) {
				pad.append("=");
				str += '\0';
			} 
		}
		StringBuffer coded = new StringBuffer();
		for (int i = 0; i < str.length(); i+=3) {
			o1 = str.charAt(i);
		    o2 = str.charAt(i+1);
		    o3 = str.charAt(i+2);
		    bits = o1<<16 | o2<<8 | o3;
		      
		    h1 = bits>>18 & 0x3f;
		    h2 = bits>>12 & 0x3f;
		    h3 = bits>>6 & 0x3f;
		    h4 = bits & 0x3f;
		   //System.err.println(h1+">"+h2+">"+h3+">"+h4);
		    coded.append(BASE64_CODE.charAt(h1)).append(BASE64_CODE.charAt(h2))
		    .append(BASE64_CODE.charAt(h3)).append(BASE64_CODE.charAt(h4));
		}
		return coded.substring(0, coded.length()-pad.length())+pad;
		
	}
	private static String fromCharCode(int... codePoints){
		return new String(codePoints, 0, codePoints.length);
	}
}
